package cn.xuqiudong.sso.server.controller;

import cn.xuqiudong.common.base.model.BaseResponse;
import cn.xuqiudong.common.base.vo.BooleanWithMsg;
import cn.xuqiudong.sso.common.constant.Oauth2Constant;
import cn.xuqiudong.sso.common.model.AccessTokenContent;
import cn.xuqiudong.sso.common.model.RefreshTokenContent;
import cn.xuqiudong.sso.common.model.RpcAccessToken;
import cn.xuqiudong.sso.common.model.SsoUser;
import cn.xuqiudong.sso.server.service.AppService;
import cn.xuqiudong.sso.server.service.Oauth2Service;
import cn.xuqiudong.sso.server.session.RefreshTokenManager;
import cn.xuqiudong.sso.server.session.TicketGrantingTicketManager;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * 描述:
 * Oauth2 controller
 *
 * @author Vic.xu
 * @since 2021-11-02 10:29
 */
@RestController
public class Oauth2Controller extends BaseSsoController {

    @Resource
    private AppService appService;

    @Resource
    private Oauth2Service oauth2Service;

    @Resource
    private RefreshTokenManager refreshTokenManager;

    @Resource
    private TicketGrantingTicketManager ticketGrantingTicketManager;


    /**
     * 通过授权码(ticket/code)获取accessToken以及用户信息
     */
    @RequestMapping(value = Oauth2Constant.ACCESS_TOKEN_URL)
    public BaseResponse<RpcAccessToken> accessToken(@RequestParam(value = Oauth2Constant.GRANT_TYPE, required = true) String grantType,
                                                    @RequestParam(value = Oauth2Constant.APP_ID, required = true) String appId,
                                                    @RequestParam(value = Oauth2Constant.APP_SECRET, required = true) String appSecret,
                                                    @RequestParam(value = Oauth2Constant.AUTH_CODE, required = false) String code,
                                                    @RequestParam(value = Oauth2Constant.USERNAME, required = false) String username,
                                                    @RequestParam(value = Oauth2Constant.PASSWORD, required = false) String password) {
        //参数检验
        BooleanWithMsg msg = oauth2Service.validateParam(grantType, code, username, password);
        if (!msg.isSuccess()) {
            return BaseResponse.error(msg.getMessage());
        }

        //应用校验
        BooleanWithMsg appIdMsg = appService.validate(appId, appSecret);
        if (!appIdMsg.isSuccess()) {
            return BaseResponse.error(appIdMsg.getMessage());
        }

        // 校验授权码 并生成token
        BaseResponse<RpcAccessToken> data = oauth2Service.validateAndGenerateToken(grantType, code, username, password, appId);

        return data;
    }

    /**
     * 利用refreshToken刷新accessToken，并延长TGT超时时间  <br />
     * <p>
     * accessToken刷新结果有两种：
     * 1. 若accessToken已超时，那么进行refreshToken会生成一个新的accessToken，新的超时时间；
     * 2. 若accessToken未超时，那么进行refreshToken不会改变accessToken，但超时时间会刷新，相当于续期accessToken。
     * 3. 并发刷新accessToken引起的问题（2022-08-18）：
     *   3.1. 第一个刷新token请求删除了server端的refreshToken，还没返回新token的时候，后一个请求就来了，导致客户端重新登录
     *   3.2. 考虑设计一个过渡性的方案，即在校验 refreshToken的时候不是立刻删除，而是暂存起来(设置其过期时间很短，如60s)，然后绑定新的accessToken，下一次请求来的时候，直接返回新的accessToken，
     * </p>
     *
     * @param appId application name
     * @param refreshToken  refreshToken
     * @return RpcAccessToken
     */
    @RequestMapping(value = Oauth2Constant.REFRESH_TOKEN_URL)
    public BaseResponse<RpcAccessToken> refreshToken(
            @RequestParam(value = Oauth2Constant.APP_ID, required = true) String appId,
            @RequestParam(value = Oauth2Constant.REFRESH_TOKEN, required = true) String refreshToken) {

        if (!appService.checkAppId(appId)) {
            return BaseResponse.error("非法应用");
        }
        RefreshTokenContent refreshTokenContent = refreshTokenManager.validate(refreshToken);
        if (refreshTokenContent == null) {
            return BaseResponse.error("refreshToken错误或已过期");
        }
        AccessTokenContent accessTokenContent = refreshTokenContent.getAccessTokenContent();
        if (!StringUtils.equals(appId, accessTokenContent.getAppId())) {
            return BaseResponse.error("非法应用");
        }


        SsoUser user = ticketGrantingTicketManager.getAndRefresh(accessTokenContent.getAuthorizationCode().getTgt());
        if (user == null) {
            return BaseResponse.error("服务端session已过期");
        }
        //如果是在更换accessToken期间，且保存了新的RpcAccessToken， 则直接返token
        if (refreshTokenContent.isRefreshDuration() && refreshTokenContent.getNewRpcAccessToken() != null) {
            return BaseResponse.success(refreshTokenContent.getNewRpcAccessToken());
        }
        //生成新的 AccessToken
        RpcAccessToken rpcAccessToken = oauth2Service.generateRpcAccessToken(accessTokenContent, refreshTokenContent.getAccessToken());

        //关联新生成的 AccessToken和原来的  RefreshTokenContent的关系， 并短暂保存
        refreshTokenContent.setNewRpcAccessToken(rpcAccessToken);
        refreshTokenManager.temporaryStorageRefreshToken(refreshToken, refreshTokenContent);

        return BaseResponse.success(rpcAccessToken);

    }
}